package com.katesoft.scale4j.persistent.hibernate;

import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.Map;

import org.hibernate.HibernateException;
import org.hibernate.cfg.Configuration;
import org.hibernate.envers.event.AuditEventListener;
import org.hibernate.event.Destructible;
import org.hibernate.event.FlushEventListener;
import org.hibernate.event.Initializable;
import org.hibernate.event.MergeEventListener;
import org.hibernate.event.PostCollectionRecreateEventListener;
import org.hibernate.event.PostCollectionRemoveEventListener;
import org.hibernate.event.PostCollectionUpdateEventListener;
import org.hibernate.event.PostDeleteEventListener;
import org.hibernate.event.PostInsertEventListener;
import org.hibernate.event.PostUpdateEventListener;
import org.hibernate.event.PreCollectionRemoveEventListener;
import org.hibernate.event.PreCollectionUpdateEventListener;
import org.hibernate.event.SaveOrUpdateEvent;
import org.hibernate.event.SaveOrUpdateEventListener;
import org.hibernate.event.def.DefaultFlushEventListener;
import org.hibernate.event.def.DefaultSaveEventListener;
import org.hibernate.event.def.DefaultSaveOrUpdateEventListener;
import org.hibernate.event.def.DefaultUpdateEventListener;
import org.hibernate.search.event.FullTextIndexEventListener;
import org.springframework.orm.hibernate3.support.IdTransferringMergeEventListener;

import com.katesoft.scale4j.log.LogFactory;
import com.katesoft.scale4j.log.Logger;

/**
 * This is internal class which is responsible for creating consolidated event listeners collection
 * combining user's event listeners and hibernate search and hibernate envers listeners.
 * 
 * @author kate2007
 */
@SuppressWarnings({ "unchecked" })
public class EventListeners implements Initializable, Destructible {
   public static final String SAVE = "save";
   public static final String UPDATE = "update";
   public static final String FLUSH = "flush";
   public static final String SAVE_UPDATE = "save-update";
   public static final String MERGE = "merge";
   public static final String POST_INSERT = "post-insert";
   public static final String POST_UPDATE = "post-update";
   public static final String POST_DELETE = "post-delete";
   public static final String PRE_COLLECTION_UPDATE = "pre-collection-update";
   public static final String PRE_COLLECTION_REMOVE = "pre-collection-remove";
   public static final String POST_COLLECTION_REMOVE = "post-collection-remove";
   public static final String POST_COLLECTION_UPDATE = "post-collection-update";
   public static final String POST_COLLECTION_RECREATE = "post-collection-recreate";
   //
   private final Logger logger = LogFactory.getLogger(EventListeners.class);
   private final AuditEventListener auditEventListener = new LocalAuditEventListener();
   private final FullTextIndexEventListener fullTextIndexEventListener = new FullTextIndexEventListener(
            FullTextIndexEventListener.Installation.SINGLE_INSTANCE);
   private final Map<String, Object> providedListeners;
   private Map<String, Object> actualListeners;

   public EventListeners(Map<String, Object> providedListeners) {
      this.providedListeners = (providedListeners != null ? providedListeners : Collections
               .<String, Object> emptyMap());
   }

   public Map<String, Object> listeners() {
      if (actualListeners == null) {
         actualListeners = new HashMap<String, Object>(providedListeners);
         //
         addSaveListeners(actualListeners);
         addFlushListeners(actualListeners);
         addUpdateListeners(actualListeners);
         addSaveOrUpdateListeners(actualListeners);
         addMergeListeners(actualListeners);
         addPostInsertListeners(actualListeners);
         addPostUpdateListeners(actualListeners);
         addPostDeleteListeners(actualListeners);
         addPreCollectionUpdateListeners(actualListeners);
         addPreCollectionRemoveListeners(actualListeners);
         addPostCollectionRemoveListeners(actualListeners);
         addPostCollectionUpdateListeners(actualListeners);
         addPostCollectionRecreateListeners(actualListeners);
      }
      return actualListeners;
   }

   @Override
   public void initialize(Configuration configuration) {
      auditEventListener.initialize(configuration);
      fullTextIndexEventListener.initialize(configuration);
      logger.debug("audit_listener=%s initialized using configuration=%s", auditEventListener,
               configuration);
      logger.debug("fullTextIndexEventListener=%s initialized using configuration=%s",
               auditEventListener, configuration);
   }

   @Override
   public void cleanup() {
      fullTextIndexEventListener.cleanup();
   }

   /**
    * add save updated listeners(client should not carry about DefaultSaveEventListener, this event
    * listener will be added as well)
    * 
    * @param temp
    *           collection of event listeners
    */
   protected void addSaveListeners(Map<String, Object> temp) {
      final Collection<Object> provided = (Collection<Object>) providedListeners.get(SAVE);
      temp.put(SAVE, new LinkedHashSet<SaveOrUpdateEventListener>() {
         private static final long serialVersionUID = 817980637661170663L;

         {
            if (provided != null) {
               for (Object next : provided) {
                  add((SaveOrUpdateEventListener) next);
               }
            }
            add(new DefaultSaveEventListener() {
               private static final long serialVersionUID = 7718350092671715161L;

               @Override
               public void onSaveOrUpdate(SaveOrUpdateEvent event) {
                  logger.trace("onSave[%s]", event.getObject());
                  super.onSaveOrUpdate(event);
               }
            });
         }
      });
   }

   /**
    * add flush listeners(client should not carry about DefaultFlushEventListener, this event
    * listener will be added as well)
    * 
    * @param temp
    *           collection of event listeners
    */
   protected void addFlushListeners(Map<String, Object> temp) {
      final Collection<Object> provided = (Collection<Object>) providedListeners.get(FLUSH);
      temp.put(FLUSH, new LinkedHashSet<FlushEventListener>() {
         private static final long serialVersionUID = -6172224625441606992L;

         {
            if (provided != null) {
               for (Object next : provided) {
                  add((FlushEventListener) next);
               }
            }
            add(fullTextIndexEventListener);
            add(new DefaultFlushEventListener());
         }
      });
   }

   /**
    * add update listeners(client should not carry about DefaultUpdateEventListener, this event
    * listener will be added as well)
    * 
    * @param temp
    *           collection of event listeners
    */
   protected void addUpdateListeners(Map<String, Object> temp) {
      final Collection<Object> provided = (Collection<Object>) providedListeners.get(UPDATE);
      temp.put(UPDATE, new LinkedHashSet<SaveOrUpdateEventListener>() {
         private static final long serialVersionUID = -198442282633707148L;

         {
            if (provided != null) {
               for (Object next : provided) {
                  add((SaveOrUpdateEventListener) next);
               }
            }
            add(new DefaultUpdateEventListener() {
               private static final long serialVersionUID = 7234302910487021949L;

               @Override
               public void onSaveOrUpdate(SaveOrUpdateEvent event) {
                  logger.trace("onUpdate[%s]", event.getObject());
                  super.onSaveOrUpdate(event);
               }
            });
         }
      });
   }

   /**
    * add save-or-update listeners(client should not carry about DefaultSaveOrUpdateEventListener,
    * this event listener will be added as well)
    * 
    * @param temp
    *           collection of event listeners
    */
   protected void addSaveOrUpdateListeners(Map<String, Object> temp) {
      final Collection<Object> provided = (Collection<Object>) providedListeners.get(SAVE_UPDATE);
      temp.put(SAVE_UPDATE, new LinkedHashSet<SaveOrUpdateEventListener>() {
         private static final long serialVersionUID = -6385361201718031198L;

         {
            if (provided != null) {
               for (Object next : provided) {
                  add((SaveOrUpdateEventListener) next);
               }
            }
            add(new DefaultSaveOrUpdateEventListener() {
               private static final long serialVersionUID = 8558210673397764847L;

               @Override
               public void onSaveOrUpdate(SaveOrUpdateEvent event) throws HibernateException {
                  logger.trace("onSaveOrUpdate[%s]", event.getObject());
                  super.onSaveOrUpdate(event);
               }
            });
         }
      });
   }

   /**
    * add merge listeners(client should not carry about DefaultMergeEventListener, this event
    * listener will be added as well)
    * 
    * @param temp
    *           collection of event listeners
    */
   protected void addMergeListeners(Map<String, Object> temp) {
      final Collection<Object> provided = (Collection<Object>) providedListeners.get(MERGE);
      temp.put(MERGE, new LinkedHashSet<MergeEventListener>() {
         private static final long serialVersionUID = 7268080774252843472L;

         {
            if (provided != null) {
               for (Object next : provided) {
                  add((MergeEventListener) next);
               }
            }
            add(new IdTransferringMergeEventListener());
         }
      });
   }

   protected void addPostInsertListeners(Map<String, Object> temp) {
      final Collection<Object> provided = (Collection<Object>) providedListeners.get(POST_INSERT);
      temp.put(POST_INSERT, new LinkedHashSet<PostInsertEventListener>() {
         private static final long serialVersionUID = 7227148091536724377L;

         {
            if (provided != null) {
               for (Object next : provided) {
                  add((PostInsertEventListener) next);
               }
            }
            add(auditEventListener);
            add(fullTextIndexEventListener);
         }
      });
   }

   protected void addPostUpdateListeners(Map<String, Object> temp) {
      final Collection<Object> provided = (Collection<Object>) providedListeners.get(POST_UPDATE);
      temp.put(POST_UPDATE, new LinkedHashSet<PostUpdateEventListener>() {
         private static final long serialVersionUID = -3242854257667664990L;

         {
            if (provided != null) {
               for (Object next : provided) {
                  add((PostUpdateEventListener) next);
               }
            }
            add(auditEventListener);
            add(fullTextIndexEventListener);
         }
      });
   }

   protected void addPostDeleteListeners(Map<String, Object> temp) {
      final Collection<Object> provided = (Collection<Object>) providedListeners.get(POST_DELETE);
      temp.put(POST_DELETE, new LinkedHashSet<PostDeleteEventListener>() {
         private static final long serialVersionUID = -952628493525340079L;

         {
            add(auditEventListener);
            add(fullTextIndexEventListener);
            if (provided != null) {
               for (Object next : provided) {
                  add((PostDeleteEventListener) next);
               }
            }
         }
      });
   }

   protected void addPreCollectionUpdateListeners(Map<String, Object> temp) {
      final Collection<Object> provided = (Collection<Object>) providedListeners
               .get(PRE_COLLECTION_UPDATE);
      temp.put(PRE_COLLECTION_UPDATE, new LinkedHashSet<PreCollectionUpdateEventListener>() {
         private static final long serialVersionUID = -3133750294762462907L;

         {
            add(auditEventListener);
            if (provided != null) {
               for (Object next : provided) {
                  add((PreCollectionUpdateEventListener) next);
               }
            }
         }
      });
   }

   protected void addPreCollectionRemoveListeners(Map<String, Object> temp) {
      final Collection<Object> provided = (Collection<Object>) providedListeners
               .get(PRE_COLLECTION_REMOVE);
      temp.put(PRE_COLLECTION_REMOVE, new LinkedHashSet<PreCollectionRemoveEventListener>() {
         private static final long serialVersionUID = -9108985069713621235L;

         {
            add(auditEventListener);
            if (provided != null) {
               for (Object next : provided) {
                  add((PreCollectionRemoveEventListener) next);
               }
            }
         }
      });
   }

   protected void addPostCollectionRemoveListeners(Map<String, Object> temp) {
      final Collection<Object> provided = (Collection<Object>) providedListeners
               .get(POST_COLLECTION_REMOVE);
      temp.put(POST_COLLECTION_REMOVE, new LinkedHashSet<PostCollectionRemoveEventListener>() {
         private static final long serialVersionUID = 8942280252892180887L;

         {
            add(fullTextIndexEventListener);
            if (provided != null) {
               for (Object next : provided) {
                  add((PostCollectionRemoveEventListener) next);
               }
            }
         }
      });
   }

   protected void addPostCollectionUpdateListeners(Map<String, Object> temp) {
      final Collection<Object> provided = (Collection<Object>) providedListeners
               .get(POST_COLLECTION_UPDATE);
      temp.put(POST_COLLECTION_UPDATE, new LinkedHashSet<PostCollectionUpdateEventListener>() {
         private static final long serialVersionUID = -2275084539050335754L;

         {
            add(fullTextIndexEventListener);
            if (provided != null) {
               for (Object next : provided) {
                  add((PostCollectionUpdateEventListener) next);
               }
            }
         }
      });
   }

   protected void addPostCollectionRecreateListeners(Map<String, Object> temp) {
      final Collection<Object> provided = (Collection<Object>) providedListeners
               .get(POST_COLLECTION_RECREATE);
      temp.put(POST_COLLECTION_RECREATE, new LinkedHashSet<PostCollectionRecreateEventListener>() {
         private static final long serialVersionUID = 935027241378192744L;

         {
            add(auditEventListener);
            add(fullTextIndexEventListener);
            if (provided != null) {
               for (Object next : provided) {
                  add((PostCollectionRecreateEventListener) next);
               }
            }
         }
      });
   }
}
